/*


 */

package jas.rpc.server;

import jas.rpc.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Reflectively selects a method from the called object and calls it with the given
 * arguments. It checks that the method may be called remotely (see {@link #isMethodRemoteInClass(Method, Class)})
 * before making the call.
 */
public class RpcListenerImpl implements RpcListener {    
    
    private static <T> boolean contains(T[] classes, T cl) {
        for (int i=0; i<classes.length; i++) {
            if (classes[i].equals(cl)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean containsMethod(Method[] methods, Method m) {
        Class[] params1 = m.getParameterTypes();
        for (Method m2 : methods) {
            if (!m.getName().equals(m2.getName()))
                continue;
            
            Class[] params2 = m.getParameterTypes();
            if (params1.length != params2.length)
                continue;
            
            boolean areEqual = true;
            for (int i=0; i<params1.length; i++) {
                if (!params1[i].equals(params2[i])) {
                    areEqual = false;
                    break;
                }
            }
            if (!areEqual)
                continue;
            
            return true;
        }
        return false;
    }
    
    /**
     * Determines if the specified method should be considered remote.
     * 
     * <p>It is a securty failure to allow clients calling non-remote methods.
     * 
     * <p>A method is remote if and only if it is declared in, or inherited from,
     * a type directly implementing the <code>Remote</code> interface.
     * 
     * @param method
     * @param clazz
     * @return
     */
    public static boolean isMethodRemoteInClass(Method method, Class clazz) {
        Class[] interfaces = clazz.getInterfaces();
        if (contains(interfaces, Remote.class)) {
            Method[] methods = clazz.getDeclaredMethods();
            if (containsMethod(methods, method)) {
                return true;
            }
        }
        else {
            for (Class inte : interfaces) {
                if (isMethodRemoteInClass(method, inte)) {
                    return true;
                }
            }
            Class superClass = clazz.getSuperclass();
            if (superClass != null && isMethodRemoteInClass(method, superClass)) {
                return true;
            }
        }
        return false;
    }
    
    public Object call(RpcRequest request) throws NoSuchMethodException, InvocationTargetException, NonRemoteMethodException {
        Object objectToCall = request.objectToCall;
        
        Method method = objectToCall.getClass().getMethod(request.methodName, request.classes);
        
        // For security, we only allow remote calls to methods placed in a class or interface that
        // directly extends Remote. So methods in Object, or subclasses cannot be accessed.
        if (!isMethodRemoteInClass(method, objectToCall.getClass())) {
            throw new NonRemoteMethodException(method.getName() + " is not remote.");
        }
        
        try {
            Object result = method.invoke(objectToCall, request.parameters);
            
            return result;
        } catch (IllegalAccessException ex) {
            throw new NoSuchMethodException(ex.getMessage());
        }
    }

}
